# DB API for PermFS

import os, pg, solver, stat

CREATE_TABLE = 'create table %(tname)s \
(fs text NOT NULL, \
path text NOT NULL, \
mode integer NOT NULL, \
uid integer NOT NULL, \
gid integer NOT NULL, \
r text, \
w text, \
x text, \
p text, \
PRIMARY KEY (fs, path));'

class Perm:
    def __init__(self, fsname):
        self.fsname = str(fsname)
        self.tname = 'perms'
        self.db = pg.DB(dbname='fuse', host='localhost', 
                        user='fuse', passwd='fuse')
        
        # Create perms table if not already there
        tables = self.db.get_tables()
        found = False
        for table in tables:
            if table == ('public.' + self.tname):
                found = True
                break
        
        # Create perms table if doesn't exists already
        if not found:
            self.db.query(CREATE_TABLE % { 'tname':self.tname })
      
    def close(self):
        # Close db connection
        self.db.close()

    ## ADD/DELETE ##

    def add_file(self, path, mode, uid, gid):
        mode = int(mode) & int('777', 8)

        try:
            self.db.insert(self.tname, { 'fs':self.fsname, 'path':str(path), 
                                         'mode':mode, 'uid':int(uid), 
                                         'gid':int(gid) })
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True

    def delete_file(self, path):
        try:
            self.db.delete(self.tname, { 'fs':self.fsname, 'path':str(path) })
        except pg.ProgrammingError:
            return False

        return True

    ## CHECKERS ##

    def check_read(self, path, uid, gids):
        try:
            metadata = self.db.get(self.tname, 
                                   { 'fs':self.fsname, 'path':str(path) })

            # Let root bypass
            if uid == 0:
                return True

            # Check if others have perms
            if metadata['mode'] & stat.S_IROTH > 0:
                return True

            # Check if owner uid and have perms
            if metadata['uid'] == uid and metadata['mode'] & stat.S_IRUSR > 0:
                return True

           # Check if owner gid and have perms
            if metadata['gid'] == gids[0] and \
                    metadata['mode'] & stat.S_IRGRP > 0:
                return True

            # Check if have perms by formula
            return solver.check_permission(uid, gids, metadata['r'])
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

    def check_write(self, path, uid, gids):
        try:
            metadata = self.db.get(self.tname, 
                                   { 'fs':self.fsname, 'path':str(path) })

            # Let root bypass
            if uid == 0:
                return True

            # Check if others have perms
            if metadata['mode'] & stat.S_IWOTH > 0:
                return True

            # Check if owner uid and have perms
            if metadata['uid'] == uid and metadata['mode'] & stat.S_IWUSR > 0:
                return True

           # Check if owner gid and have perms
            if metadata['gid'] == gids[0] and \
                    metadata['mode'] & stat.S_IWGRP > 0:
                return True

            return solver.check_permission(uid, gids, metadata['w'])
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False
    
    def check_execute(self, path, uid, gids):
        try:
            metadata = self.db.get(self.tname, 
                                   { 'fs':self.fsname, 'path':str(path) })
            # Let root bypass
            if uid == 0:
                return True

            # Check if others have perms
            if metadata['mode'] & stat.S_IXOTH > 0:
                return True

            # Check if owner uid and have perms
            if metadata['uid'] == uid and metadata['mode'] & stat.S_IXUSR > 0:
                return True

           # Check if owner gid and have perms
            if metadata['gid'] == gids[0] and \
                    metadata['mode'] & stat.S_IXGRP > 0:
                return True

            # Check if have perms by formula
            return solver.check_permission(uid, gids, metadata['x'])
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

    def check_perm(self, path, uid, gids):
        try:
            metadata = self.db.get(self.tname, 
                                   { 'fs':self.fsname, 'path':str(path) })

            # Let root bypass
            if uid == 0:
                return True

            # Check if owner
            if metadata['uid'] == uid:
                return True

            # Check if have perms by formula
            return solver.check_permission(uid, gids, metadata['p'])
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

    ## GETTERS ##
        
    def get(self, path):
        try:
            return self.db.get(self.tname,
                               { 'fs':self.fsname, 'path':str(path) })
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None

    def get_mode(self, path):
        try:
            return self.db.get(self.tname,
                               { 'fs':self.fsname, 'path':str(path) })['mode']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None

    def get_uid(self, path):
        try:
            return self.db.get(self.tname, 
                               { 'fs':self.fsname, 'path':str(path) })['uid']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None

    def get_gid(self, path):
        try:
            return self.db.get(self.tname, 
                               { 'fs':self.fsname, 'path':str(path) })['gid']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None

    def get_r(self, path):
        try:
            return self.db.get(self.tname, 
                               { 'fs':self.fsname, 'path':str(path) })['r']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None
    
    def get_w(self, path):
        try:
            return self.db.get(self.tname, 
                               { 'fs':self.fsname, 'path':str(path) })['w']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None

    def get_x(self, path):
        try:
            return self.db.get(self.tname, 
                               { 'fs':self.fsname, 'path':str(path) })['x']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None
    
    def get_p(self, path):
        try:
            return self.db.get(self.tname, 
                               { 'fs':self.fsname, 'path':str(path) })['p']
        except pg.ProgrammingError:
            return None
        except pg.DatabaseError:
            return None

    def entry_exists(self, path):
        try:
            self.db.get(self.tname, 
                        { 'fs':self.fsname, 'path':str(path) })
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False
        
        return True

    ## UPDATERS ##

    def update_owner(self, path, uid, gid):
        try:
            self.db.update(self.tname, { 'fs':self.fsname, 'path':str(path) }, 
                                         uid=int(uid), gid=int(gid))
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True

    def update_mode(self, path, mode):
        mode = int(mode) & int('777', 8)

        try:
            self.db.update(self.tname, { 'fs':self.fsname, 'path':str(path) }, 
                           mode=mode)
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True

    def update_r(self, path, r):
        if not solver.validate(r):
            return False

        try:
            self.db.update(self.tname, { 'fs':self.fsname, 'path':str(path) }, 
                           r=str(r))
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True

    def update_w(self, path, w):
        if not solver.validate(w):
            return False

        try:
            self.db.update(self.tname, { 'fs':self.fsname, 'path':str(path) }, 
                           w=str(w))
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True

    def update_x(self, path, x):
        if not solver.validate(x):
            return False

        try:
            self.db.update(self.tname, { 'fs':self.fsname, 'path':str(path) }, 
                           x=str(x))
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True

    def update_p(self, path, p):
        if not solver.validate(p):
            return False

        try:
            self.db.update(self.tname, { 'fs':self.fsname, 'path':str(path) },
                           p=str(p))
        except pg.ProgrammingError:
            return False
        except pg.DatabaseError:
            return False

        return True
